//      Copyright (c) Microsoft Corporation. All rights reserved.
#include <windows.h>
#include "dpwssideshow.h"

CDeviceProxyProvider::CDeviceProxyProvider()
{
}

CDeviceProxyProvider::~CDeviceProxyProvider()
{
    DeleteCriticalSection(&m_CriticalSection);
}

HRESULT CDeviceProxyProvider::Initialize(const wstring& clientID, 
                     CSideShowButtonEventSink *pEventSink)
{
    CComObject<CDPWSSideShowEventSink>* pDPWSEventSink;

    try
    {
        InitializeCriticalSection(&m_CriticalSection);
    }
    catch(...)
    {
        return E_OUTOFMEMORY;
    }

    HRESULT hr;
    // create event sink
    hr = CComObject<CDPWSSideShowEventSink>::CreateInstance(&pDPWSEventSink);
    if (S_OK != hr)
        return hr;

    pDPWSEventSink->Initialize(clientID, pEventSink);
    m_pDPWSEventSink = pDPWSEventSink;

    m_pEventSink = pEventSink;
    return S_OK;
}

void CDeviceProxyProvider::SendRenderAgain()
{
    m_pEventSink->OnButton(CDevice::RenderAgain);
}

CDeviceDiscovery::CDeviceDiscovery()
{
}

HRESULT CDeviceDiscovery::Initialize(const wstring& instance, 
                     CDeviceProxyProvider* pDeviceProxyProvider)
{
    HRESULT hr;

    if (!pDeviceProxyProvider)
        return E_FAIL;

    wstring deviceID;
    // create function discovery
    hr = CoCreateInstance(
        __uuidof(FunctionDiscovery),
        NULL,
        CLSCTX_INPROC_SERVER,
        __uuidof(IFunctionDiscovery),
        reinterpret_cast<void**>(&m_pFunctionDiscovery)
        );

    if (S_OK != hr)
        return hr;

    hr = GetDevicePNPXID(instance, deviceID);

#if DBG
    WCHAR wszBuff[256];
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), 
        L"Get device PNPX ID  %s\r\n", 
        deviceID.data());
    OutputDebugString(wszBuff);
#endif

    if (S_OK != hr)
        return hr;

    hr = pDeviceProxyProvider->UpdateProxy(deviceID);

    if (S_OK != hr)
        return hr;

    // listen to WSD function discovery provider
    CFunctionDiscoveryListener* pListener;
    pListener = new(std::nothrow) CFunctionDiscoveryListener(deviceID, pDeviceProxyProvider);

    if (!pListener)
        return E_OUTOFMEMORY;

    hr = StartListener(pListener);
    pListener->Release();
    return hr;
}

HRESULT CDeviceDiscovery::StartListener(CFunctionDiscoveryListener* pListener)
{
    HRESULT hr;

    // Create instance query object, and start query process to obtain function
    // instance list pointer
    CComPtr<IFunctionInstanceCollection> pFunInsts = NULL;
    hr = m_pFunctionDiscovery->CreateInstanceCollectionQuery(
        FCTN_CATEGORY_WSDISCOVERY,
        NULL,
        FALSE,
        pListener,
        NULL,
        &m_pFunInstsQuery);

    if (S_OK != hr)
        return hr;

        // add property constraint
        hr = m_pFunInstsQuery->AddQueryConstraint(
        FD_QUERYCONSTRAINT_VISIBILITY,
            FD_CONSTRAINTVALUE_VISIBILITY_ALL);

    if (S_OK != hr)
        return hr;

    hr = m_pFunInstsQuery->Execute(&pFunInsts);
    
    if (E_PENDING == hr)
        return S_OK;

    return hr;
}

HRESULT CDeviceDiscovery::GetDevicePNPXID(const wstring& instance, wstring& deviceID)
{
    HRESULT hr;

    // query device function instance and get device ID
    CComPtr<IFunctionInstanceCollectionQuery> pFunInstsQuery = NULL;
    CComPtr<IFunctionInstanceCollection> pFunInsts = NULL;

    hr = m_pFunctionDiscovery->CreateInstanceCollectionQuery(
            FCTN_CATEGORY_PNP,
            NULL,
            FALSE,
            NULL,
            NULL,
            &pFunInstsQuery
            );

    // Add query constraint. The instance ID is actually the device instance path
    if (S_OK == hr)
    {
        hr = pFunInstsQuery->AddQueryConstraint(
            FD_QUERYCONSTRAINT_PROVIDERINSTANCEID,
            instance.data());
    }

    if (S_OK == hr)
    {
        hr = pFunInstsQuery->Execute(&pFunInsts);
    }

    // Get the only instance in function instance list
    if(S_OK != hr)
        return hr;

    if (pFunInsts == NULL)
        return E_FAIL;
    
    CComPtr<IFunctionInstance> pFunInst = NULL;
    hr = pFunInsts->Item(0, &pFunInst);

    if (S_OK != hr)
        return hr;

    if (NULL == pFunInst)
        return E_FAIL;

    return GetFunctionInstancePNPXID(pFunInst, deviceID);
}

HRESULT GetFunctionInstancePNPXID(IFunctionInstance* pFunInst, wstring& deviceID)
{
    HRESULT hr;

    CComPtr<IPropertyStore> pPropStore = NULL;
    hr = pFunInst->OpenPropertyStore( STGM_READ, &pPropStore );

    if (hr != S_OK)
        return hr;

    PROPVARIANT pv = {0};
    hr = pPropStore->GetValue(PKEY_PNPX_GlobalIdentity, &pv);

    if (hr != S_OK)
        return hr;

    deviceID = pv.pwszVal;
    // Cleanup
    PropVariantClear(&pv); 
    return S_OK;
}

void CDeviceProxyProvider::GetProxy(CComPtr<ISideShowServiceTypeProxy>& pProxy)
{
    EnterCriticalSection(&m_CriticalSection);
    pProxy =  m_pDeviceProxy;
    LeaveCriticalSection(&m_CriticalSection);
#if DBG
    WCHAR wszBuff[256];
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), 
        L"GetProxy  %x\r\n", 
        pProxy.p);
    OutputDebugString(wszBuff);
#endif
}

void CDeviceProxyProvider::SetProxy(const CComPtr<ISideShowServiceTypeProxy>& pProxy)
{
#if DBG
    WCHAR wszBuff[256];
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), 
        L"SetProxy  %x, release %x\r\n", 
        pProxy.p, m_pDeviceProxy.p);
    OutputDebugString(wszBuff);
#endif

    CComPtr<ISideShowServiceTypeProxy> pTempProxy(m_pDeviceProxy);

    EnterCriticalSection(&m_CriticalSection);
    m_pDeviceProxy = pProxy;
    LeaveCriticalSection(&m_CriticalSection);

    SendRenderAgain();

    if (!pTempProxy)
        return;

    // unsubscribe event is needed by WSD API to release objects
    UnsubscribeToEvents(pTempProxy);
}

HRESULT CDeviceProxyProvider::UpdateProxy(const wstring& deviceID)
{
#if DBG
    {
        WCHAR wszBuff[256];
        StringCchPrintf(wszBuff, COUNTOF(wszBuff), 
            L"UpdateProxy deviceID %s\r\n", 
            deviceID.data());
        OutputDebugString(wszBuff);
    }
#endif

    CComPtr<ISideShowServiceTypeProxy> pProxy;
    HRESULT hr = CreateProxy(deviceID, pProxy);
    if (S_OK != hr)
    {
#if DBG
        WCHAR wszBuff[256];
        StringCchPrintf(wszBuff, COUNTOF(wszBuff), 
            L"CreateProxy failed, hr = %x\r\n", 
            hr);
        OutputDebugString(wszBuff);
#endif
        return hr;
    }

    SetProxy(pProxy);
    return S_OK;
}

HRESULT CDeviceProxyProvider::CreateProxy(const wstring& deviceID, CComPtr<ISideShowServiceTypeProxy>& pProxy)
{
    // Create a unique proxy ID to use when connecting to device
    UUID uuid;
    WCHAR szProxyAddress[MAX_PATH] = {0};

    HRESULT hr = UuidCreate( &uuid );
    if( S_OK != hr )
        return hr;
    
    hr = StringCbPrintfW(
        szProxyAddress, sizeof(szProxyAddress),
        L"urn:uuid:%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
        uuid.Data1, uuid.Data2, uuid.Data3,
        uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3],
        uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]);
    
    if (S_OK != hr)
        return hr;

    CSideShowServiceTypeProxy* temp;
    CComPtr<ISideShowServiceTypeProxy> tempProxy;

    hr = CreateCSideShowServiceTypeProxy(
                deviceID.data(),
                szProxyAddress,
                &temp,
                NULL);

    if (S_OK != hr)
        return hr;

    tempProxy.Attach(temp);

    hr = SubscribeToEvents(temp);

    if (S_OK != hr)
        return hr;

    pProxy = tempProxy;

    return S_OK;
}

HRESULT CDeviceProxyProvider::SubscribeToEvents(ISideShowServiceTypeProxy* pProxy)
{
    return pProxy->SubscribeToSideShowButtonPressedEvent(m_pDPWSEventSink);
}

HRESULT CDeviceProxyProvider::UnsubscribeToEvents(ISideShowServiceTypeProxy* pProxy)
{
    return pProxy->UnsubscribeToSideShowButtonPressedEvent();
}